package com.xcity.tools.http;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.util.TypeUtils;
import com.xcity.Server;
import com.xcity.game.battle.pve.template.StageTemplate;
import com.xcity.game.employee.template.EmployeeComboTemplate;
import com.xcity.game.employee.template.EmployeeQualityUpTemplate;
import com.xcity.game.employee.template.EmployeeTemplate;
import com.xcity.tools.http.impl.EmployeeComboTemplateFilter;
import com.xcity.tools.http.impl.EmployeeQualityUpTemplateFilter;
import com.xcity.tools.http.impl.EmployeeTemplateFilter;
import com.xcity.tools.http.impl.StageTemplateFilter;
import com.xcity.tools.http.template.MenuOpenTemplate;
import com.xcity.tools.template.TemplateClassCreater;
import com.xcity.tools.template.TemplateClassCreater.Head;

import naga.x.App;
import naga.x.common.Config;
import naga.x.game.template.Template;
import naga.x.game.template.TemplateParser;
import naga.x.game.template.TemplateService;
import naga.x.game.template.annotation.Excel;
import naga.x.platform2.support.FastJsonHttpMessageConverterImpl;
import naga.x.util.IOUtils;
import naga.x.util.StringUtils;

@SpringBootApplication
@ServletComponentScan
@Controller
public class HttpService extends WebMvcConfigurerAdapter implements EmbeddedServletContainerCustomizer, ErrorController {

	public static boolean needInit = false;
	
	@Autowired
	HttpXLSTools httpCfg;
	
	public void customize(ConfigurableEmbeddedServletContainer container) {
	}
	/*
	@Deprecated
	static final String DIR_XLS = "D:/p3/gd_packet/01-配置表";//"data/";//httpCfg.getDataDir();//
	@Deprecated
	static Map<XlsKey, Class<? extends Template>> templateClasses = new LinkedHashMap<>(); // key: xls-sheet
	@Deprecated
	static List<XlsStruct> templateStructs = new ArrayList<>();
	@Deprecated
	static TemplateService service = new TemplateService();
	@Deprecated
	static String html;
	@Deprecated
	static Map<Class<? extends Template>, XLS2JSONFilter<? extends Template>> filters = new LinkedHashMap<>();
	*/
	static class XlsKey {
		String xls, sheet;
		XlsKey(String xls, String sheet) {
			this.xls = xls;
			this.sheet = sheet;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((sheet == null) ? 0 : sheet.hashCode());
			result = prime * result + ((xls == null) ? 0 : xls.hashCode());
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			XlsKey other = (XlsKey) obj;
			if (sheet == null) {
				if (other.sheet != null)
					return false;
			} else if (!sheet.equals(other.sheet))
				return false;
			if (xls == null) {
				if (other.xls != null)
					return false;
			} else if (!xls.equals(other.xls))
				return false;
			return true;
		}
	}
	/*
	static {
		JSON.DEFAULT_GENERATE_FEATURE = SerializerFeature.config(JSON.DEFAULT_GENERATE_FEATURE, SerializerFeature.WriteEnumUsingToString, false);
		JSON.DEFAULT_GENERATE_FEATURE = SerializerFeature.config(JSON.DEFAULT_GENERATE_FEATURE, SerializerFeature.WriteEnumUsingName, false);
		JSON.DEFAULT_GENERATE_FEATURE = SerializerFeature.config(JSON.DEFAULT_GENERATE_FEATURE, SerializerFeature.PrettyFormat, true);
		TypeUtils.compatibleWithJavaBean = true;
		
		App.getApp().setConfig(new Config());
		App.getApp().getServiceManager().add(service);
//		App.getApp().getConfig().put(Config.DATA_DIR, DIR_XLS);
		App.getApp().getConfig().dataDir = DIR_XLS;
		initFilters();
		registerTemplates();
		readTemplatesStruct();
		readHtml();
	}
	
	static void initFilters() {
		filters.put(EmployeeTemplate.class, new EmployeeTemplateFilter());
		filters.put(EmployeeQualityUpTemplate.class, new EmployeeQualityUpTemplateFilter());
		filters.put(StageTemplate.class, new StageTemplateFilter());
		filters.put(EmployeeComboTemplate.class, new EmployeeComboTemplateFilter());
	}
	
	static void readHtml() {
		html = IOUtils.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("xls2json.html"));
	}
	*/
	
	public static class XlsStruct {
		public String name; // xls name
		public List<SheetStruct> sheets = new ArrayList<>();
		XlsStruct(String name) {
			this.name = name;
		}
	}
	
	public static class SheetStruct implements Comparable<SheetStruct> {
		private int index;
		public String name; // sheet name
		public List<SheetField> fields = new ArrayList<>();
		SheetStruct(int index, String name) {
			this.index = index;
			this.name = name;
		}
		@Override
		public int compareTo(SheetStruct o) {
			return index - o.index;
		}
	}
	
	public static class SheetField {
		public String name;
		public String desc;
		public SheetField(String name, String desc) {
			this.name = StringUtils.isNotEmpty(name) ? name : "";
			this.desc = desc;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((desc == null) ? 0 : desc.hashCode());
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			}
			if (obj != null && obj instanceof SheetField) {
				return name.equals(((SheetField) obj).name);
			}
			return false;
		}
	}
	/*
	static XlsStruct find(String xls) {
		List<XlsStruct> list = templateStructs;
		for (int i = list.size() - 1; i >= 0; i--) {
			XlsStruct xs = list.get(i);
			if (xls.equals(xs.name)) {
				return xs;
			}
		}
		XlsStruct xs = new XlsStruct(xls);
		list.add(xs);
		return xs;
	}
	*/
	@RequestMapping(path = "/tools/xlsstruct", produces = "application/json; charset=UTF-8")
	@ResponseBody
	public Object requestXlsStructure() throws Exception {
		return httpCfg.tryGetTemplateStructs();//templateStructs;
	}
	
//	text/html;charset=utf-8
	@RequestMapping(path = "/tools/xls2json", produces = "text/html; charset=UTF-8")
	@ResponseBody
	public String init() {
		return httpCfg.tryGetHtml();
	}
	
	@RequestMapping(path = "/tools/xls2json", produces = "application/json; charset=UTF-8")
	@ResponseBody
	public Object requestXlsToJson(String xls, String sheet, String fields, Integer pretty) {
		if (StringUtils.isEmpty(xls)) {
			return httpCfg.getDataDir();//html;
		}
		
		Set<String> fieldSet = Collections.emptySet();
		try {
			fieldSet = translateFields(fields);
		} catch (Exception e) {
			e.printStackTrace();
			return e.getMessage();
		}
		if (fieldSet.isEmpty()) {
			return "please select fields";
		}
		Class<? extends Template> templateClass = httpCfg.getTemplateClass(xls, sheet);
		if (templateClass == null) {
			return "xls not found: " + xls + "->" + sheet;
		}
		List<? extends Template> templates = httpCfg.tryGetService().getTemplatesList(templateClass);//service
		if (templates == null) {
			return "xls template not parsed: " + templateClass;
		}
		try {
			XLS2JSONFilter<Template> filter = (XLS2JSONFilter<Template>) httpCfg.tryGetFilters().get(templateClass);//filters
			if (filter != null) {
				Object val = filter.doFilterAll((List<Template>) templates, fieldSet);
				if (val != null) {
					return val;
				}
			}
			JSONArray jsonArr = new JSONArray(templates.size());
			for (Template template : templates) {
				JSONObject jsonObj = new JSONObject(true);
				jsonArr.add(jsonObj);
				for (String field : fieldSet) {
					Method getter = fetchGetterMethod(templateClass, field);
					if (getter != null) {
						Object fieldValue;
						if (filter != null) {
							fieldValue = filter.doFilter(template, field, getter);
						} else {
							fieldValue = getter.invoke(template);
						}
						jsonObj.put(field, fieldValue);
					}
				}
			}
			
//			BeanInfo bean = Introspector.getBeanInfo(templateClass, Object.class);
//			PropertyDescriptor[] prpts = bean.getPropertyDescriptors();
//			for (Template template : templates) {
//				JSONObject jsonObj = new JSONObject(true);
//				jsonArr.add(jsonObj);
//				for (PropertyDescriptor p : prpts) {
//					if (fieldSet.contains(p.getName())) {
//						Method getter = p.getReadMethod();
//						Object fieldValue;
//						if (filter != null) {
//							fieldValue = filter.doFilter(template, p.getName(), getter);
//						} else {
//							fieldValue = getter.invoke(template);
//						}
//						jsonObj.put(p.getName(), fieldValue);
//					}
//				}
//			}
			return jsonArr;
		} catch (Exception e) {
			e.printStackTrace();
			return e.getMessage();
		}
	}
	
	public static Method fetchGetterMethod(Class<?> cls, String fieldName) {
		fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		String methodName = "get" + fieldName;
		Method method = null;
		try {
			method = cls.getMethod(methodName);
			if (method != null) {
				methodName = "is" + fieldName;
				method = cls.getMethod(methodName);
			}
		} catch (Exception e) {}
		return method;
	}
	
	static Set<String> translateFields(String fields) {
		Set<String> set = new LinkedHashSet<>();
		String[] ss = StringUtils.split(fields, ",");
		for (String s : ss) {
			if (StringUtils.isEmpty(s)) {
				throw new RuntimeException("fields error: " + fields);
			}
			set.add(s.trim());
		}
		return set;
	}
	/*
	static void registerUserDefinedTemplates() {
		TemplateService.register(MenuOpenTemplate.class);
		TemplateService.register(EmployeeComboTemplate.class);
	}
	
	static void registerTemplates() {
		try {
			Method method = Server.class.getDeclaredMethod("registerTemplates");
			method.setAccessible(true);
			method.invoke(null);
			registerUserDefinedTemplates();
			
			Field field = TemplateService.class.getDeclaredField("registeredTemplateClasses");
			field.setAccessible(true);
			Set<Class<? extends Template>> classes = (Set<Class<? extends Template>>) field.get(null);
			TemplateParser parser = new TemplateParser(service);
			for (Class<? extends Template> cls : classes) {
				System.out.println("parse try:" + cls);
				Excel anno = cls.getDeclaredAnnotation(Excel.class);
				if (!anno.isStatic()) {
					File xls = new File(DIR_XLS, anno.value());
					Workbook workbook;
					if (xls.getName().endsWith(".xlsx")) {
						workbook = new XSSFWorkbook(xls);
					} else {
						workbook = new HSSFWorkbook(new FileInputStream(xls));
					}
					Sheet sheet = StringUtils.isNotEmpty(anno.sheetName()) ? workbook.getSheet(anno.sheetName()) : workbook.getSheetAt(anno.sheet());
					XlsKey key = new XlsKey(anno.value(), sheet.getSheetName());
					templateClasses.put(key, cls);
					workbook.close();
					
					parser.parse(cls);
				}
				System.out.println("parse ok:" + cls);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	static Class<? extends Template> getTemplateClass(String xls, String sheet) {
		return templateClasses.get(new XlsKey(xls, sheet));
	}

	static void readTemplatesStruct() {
		try {
//			Set<XlsKey> set = templateClasses.keySet();
			Set<Entry<XlsKey, Class<? extends Template>>> set = templateClasses.entrySet();
			for (Entry<XlsKey, Class<? extends Template>> entry : set) {
				XlsKey xk = entry.getKey();
				TemplateClassCreater.SHEET = -1;
				TemplateClassCreater.SHEET_NAME = xk.sheet;
				Workbook wb = TemplateClassCreater.getWorkbook(new File(DIR_XLS, xk.xls));
				List<Head> heads = TemplateClassCreater.readHead(wb);
				
				XlsStruct struct = find(xk.xls);
				SheetStruct sheetStruct = new SheetStruct(TemplateClassCreater.sheetIndex, TemplateClassCreater.sheetName);
				XLS2JSONFilter<? extends Template> filter = filters.get(entry.getValue());
				List<SheetField> fields = filter != null ? filter.getFields() : null;
				if (fields != null) {
					sheetStruct.fields = fields;
				} else {
					for (Head head : heads) {
						SheetField sf = new SheetField(head.name, head.desc);
						if (StringUtils.isNotEmpty(head.name) && !sheetStruct.fields.contains(sf)) {
							sheetStruct.fields.add(sf);
						}
					}
				}
				struct.sheets.add(sheetStruct);
			}
			for (XlsStruct struct : templateStructs) {
				Collections.sort(struct.sheets);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	*/
	@Override
	public String getErrorPath() {
		return "/error";
	}
	
	@RequestMapping(path = "/error", produces = "application/json; charset=UTF-8")
	@ResponseBody
	public Object handleError() {
		return "error";
	}
	
	@Bean
	public HttpMessageConverters getConverters() {
		HttpMessageConverter<?> fastjson = new FastJsonHttpMessageConverterImpl();
		return new HttpMessageConverters(fastjson);
	}
	
}
